/**
 *  重排序、内存可见性以及原子性
 *
 *  重排序
 *      编译器优化：对于没有数据依赖关系的操作，编译器在编译的过程中会进行一定程度的重排。
 *      指令重排序：CPU 优化行为，也是会对不存在数据依赖关系的指令进行一定程度的重排。
 *      内存系统重排序：内存系统没有重排序，但是由于有缓存的存在，使得程序整体上会表现出乱序的行为。
 *
 *  内存可见性
 *    线程间的对于共享变量的可见性问题不是直接由多核引起的，而是由多缓存引起的。如果每个核心共享同一个缓存，那么也就不存在内存可见性问题了。
 *    现代多核 CPU 中每个核心拥有自己的一级缓存或一级缓存加上二级缓存等，问题就发生在每个核心的独占缓存上。每个核心都会将自己需要的数据读到独占缓存中，
 *    数据修改后也是写入到缓存中，然后等待刷入到主存中。所以会导致有些核心读取的值是一个过期的值。
 *
 *    Java 作为高级语言，屏蔽了这些底层细节，用 JMM 定义了一套读写内存数据的规范，虽然我们不再需要关心一级缓存和二级缓存的问题，但是，JMM 抽象了主内存和本地内存的概念。
 *    所有的共享变量存在于主内存中，每个线程有自己的本地内存，线程读写共享数据也是通过本地内存交换的，所以可见性问题依然是存在的。
 *
 *
 *  原子性
 *      long 和 double，它们的值需要占用 64 位的内存空间，Java 编程语言规范中提到，对于 64 位的值的写入，可以分为两个 32 位的操作进行写入。
 *      本来一个整体的赋值操作，被拆分为低 32 位赋值和高 32 位赋值两个操作，中间如果发生了其他线程对于这个值的读操作，必然就会读到一个奇怪的值。
 *      这个时候要使用 volatile 关键字进行控制了，JMM 规定了对于 volatile long 和 volatile double，JVM 需要保证写入操作的原子性。
 *      另外，对于引用的读写操作始终是原子的，不管是 32 位的机器还是 64 位的机器。
 *
 *  Synchronization Order
 *      对于监视器 m 的解锁与所有后续操作对于 m 的加锁同步
 *      对 volatile 变量 v 的写入，与所有其他线程后续对 v 的读同步
 *      启动线程的操作与线程中的第一个操作同步。
 *      对于每个属性写入默认值（0， false，null）与每个线程对其进行的操作同步。尽管在创建对象完成之前对对象属性写入默认值有点奇怪，但从概念上来说，每个对象都是在程序启动时用默认值初始化来创建的。
 *      线程 T1 的最后操作与线程 T2 发现线程 T1 已经结束同步。
 *      线程 T2 可以通过 T1.isAlive() 或 T1.join() 方法来判断 T1 是否已经终结。
 *      如果线程 T1 中断了 T2，那么线程 T1 的中断操作与其他所有线程发现 T2 被中断了同步（通过抛出 InterruptedException 异常，或者调用 Thread.interrupted 或 Thread.isInterrupted ）
 *
 *
 *  Happens-before Order
 *   如果一个操作 happens-before 于另一个操作，那么我们说第一个操作对于第二个操作是可见的。
 *   这里再提一点，x happens-before y，并不是说 x 操作一定要在 y 操作之前被执行，而是说 x 的执行结果对于 y 是可见的，只要满足可见性，发生了重排序也是可以的
 *
 *   如果我们分别有操作 x 和操作 y，我们写成 hb(x, y) 来表示 x happens-before y。
 *      如果操作 x 和操作 y 是同一个线程的两个操作，并且在代码上操作 x 先于操作 y 出现，那么有 hb(x, y)
 *      对象构造方法的最后一行指令 happens-before 于 finalize() 方法的第一行指令。
 *      如果操作 x 与随后的操作 y 构成同步，那么 hb(x, y)。hb(x, y) 和 hb(y, z)，那么可以推断出 hb(x, z)
 *
 *
 * synchronized 关键字
 *      原子性，可见性，禁止指令重排序
 *      一个线程在获取到监视器锁以后才能进入 synchronized 控制的代码块，一旦进入代码块，首先，该线程对于共享变量的缓存就会失效，
 *      因此 synchronized 代码块中对于共享变量的读取需要从主内存中重新获取，也就能获取到最新的值。
 *
 *      退出代码块的时候的，会将该线程写缓冲区中的数据刷到主内存中，所以在 synchronized 代码块之前或 synchronized
 *      代码块中对于共享变量的操作随着该线程退出 synchronized 块，会立即对其他线程可见（这句话的前提是其他读取共享变量的线程会从主内存读取最新值）。
 *
 *  volatile 关键字
 *    内存可见性和禁止指令重排序。
 *    内存可见性语义: 读一个 volatile 变量之前，需要先使相应的本地缓存失效，这样就必须到主内存读取最新值，写一个 volatile 属性会立即刷入到主内存。
 *    禁止重排序
 *      volatile 修饰符适用于以下场景：某个属性被多个线程共享，其中有一个线程修改了此属性，其他线程可以立即得到修改后的值。在并发包的源码中，它使用得非常多。
 *      volatile 属性的读写操作都是无锁的，它不能替代 synchronized，因为它没有提供原子性和互斥性。因为无锁，不需要花费时间在获取锁和释放锁上，所以说它是低成本的。
 *      volatile 只能作用于属性，我们用 volatile 修饰属性，这样 compilers 就不会对这个属性做指令重排序。
 *      volatile 提供了可见性，任何一个线程对其的修改将立马对其他线程可见。volatile 属性不会被线程缓存，始终从主存中读取。
 *      volatile 提供了 happens-before 保证，对 volatile 变量 v 的写入 happens-before 所有其他线程后续对 v 的读操作。
 *      volatile 可以使得 long 和 double 的赋值是原子的，前面在说原子性的时候提到过
 *
 *  final 关键字
 *   用 final 修饰的类不可以被继承，用 final 修饰的方法不可以被覆写，用 final 修饰的属性一旦初始化以后不可以被修改。
 *   在对象的构造方法中设置 final 属性，同时在对象初始化完成前，不要将此对象的引用写入到其他线程可以访问到的地方（不要让引用在构造函数中逸出）。
 *   如果这个条件满足，当其他线程看到这个对象的时候，那个线程始终可以看到正确初始化后的对象的 final 属性。
 *
 *
 *   单例模式中的双重检查
 *
 */
package cn.hy.concurrent.内存模型;